#include "koch.h"
#include <math.h>
#include <GL/gl.h>
#include "../element/triangle.h"

Koch::Koch()
{
}

Koch::Koch(Box& box, Point &pt, double r, int l)
{
    bbox = box;
    ct = pt;
    radius = r;
    level = l;
}

Koch::~Koch()
{
}

void Koch::generate()
{
    quitThread = false;

    allBoxes.clear();
    allBoxes.push_back(bbox);

    // generateFang(bbox, level);
    generateDM(bbox, level);

    latticeBoxes.clear();
    latticeBoxes.push_back(bbox);
}

void Koch::draw()
{
    for (size_t i = 0; i < allBoxes.size(); i++)
    {
        allBoxes[i].draw();
    }

    // drawLattice();
}

void Koch::drawLattice()
{
    glColor3d(0.0, 0.0, 0.0);
    glLineWidth(2.0);

    double h = bbox.height()/2;
    double y = 0;
    for (int i = 0; ;i++)
    {
        if (y >= h)
        {
            break;
        }
        glBegin(GL_LINES);
        glVertex2d(bbox.x0, y);
        glVertex2d(bbox.x1, y);
        glVertex2d(bbox.x0, -y);
        glVertex2d(bbox.x1, -y);

        if (i % 2 == 0)
        {
            glVertex2d(bbox.x0, y);
            glVertex2d(bbox.x0+(h-y)/sqrt(3), bbox.y1);
            glVertex2d(bbox.x0, y);
            glVertex2d(bbox.x0+(h+y)/sqrt(3), bbox.y0);

            glVertex2d(bbox.x0, -y);
            glVertex2d(bbox.x0+(h-y)/sqrt(3), bbox.y0);
            glVertex2d(bbox.x0, -y);
            glVertex2d(bbox.x0+(h+y)/sqrt(3), bbox.y1);

            glVertex2d(bbox.x1, y);
            glVertex2d(bbox.x1-(h-y)/sqrt(3), bbox.y1);
            glVertex2d(bbox.x1, y);
            glVertex2d(bbox.x1-(h+y)/sqrt(3), bbox.y0);

            glVertex2d(bbox.x1, -y);
            glVertex2d(bbox.x1-(h-y)/sqrt(3), bbox.y0);
            glVertex2d(bbox.x1, -y);
            glVertex2d(bbox.x1-(h+y)/sqrt(3), bbox.y1);
        }

        glEnd();

        y += radius * sqrt(3)/2;
    }

    double w = bbox.width()/2 - h/sqrt(3);
    for (double x = 0; x < w;)
    {
        glBegin(GL_LINES);
        glVertex2d(x - h/sqrt(3), bbox.y0);
        glVertex2d(x + h/sqrt(3), bbox.y1);
        glVertex2d(x - h/sqrt(3), bbox.y1);
        glVertex2d(x + h/sqrt(3), bbox.y0);

        glVertex2d(-x - h/sqrt(3), bbox.y0);
        glVertex2d(-x + h/sqrt(3), bbox.y1);
        glVertex2d(-x - h/sqrt(3), bbox.y1);
        glVertex2d(-x + h/sqrt(3), bbox.y0);

        glEnd();

        x += radius;
    }

    glBegin(GL_LINE_LOOP);
    glVertex2d(bbox.x0, bbox.y0);
    glVertex2d(bbox.x1, bbox.y0);
    glVertex2d(bbox.x1, bbox.y1);
    glVertex2d(bbox.x0, bbox.y1);
    glEnd();

    glColor4d(0.4, 0.4, 0.4, 1.0);
    for (int i = 0; i < 4; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * i, -sqrt(3)*radius);
        glVertex2d(bbox.x0 + radius * (i+1), -sqrt(3)*radius);
        glVertex2d(bbox.x0 + radius * (i+1) - (h/sqrt(3)-radius), bbox.y0);
        glVertex2d(bbox.x0 + radius * i + (h/sqrt(3)-radius), bbox.y0);
        glEnd();
    }

    for (int i = 0; i < 3; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * i + radius/2, -sqrt(3)/2*radius);
        glVertex2d(bbox.x0 + radius * (i+1) + radius/2, -sqrt(3)/2*radius);
        glVertex2d(bbox.x0 + radius * (i+1), -sqrt(3)*radius);
        glEnd();
    }

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x0, -sqrt(3)/2*radius);
    glVertex2d(bbox.x0 + radius/2, -sqrt(3)/2*radius);
    glVertex2d(bbox.x0, -sqrt(3)*radius);
    glEnd();

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x1, -sqrt(3)/2*radius);
    glVertex2d(bbox.x1 - radius/2, -sqrt(3)/2*radius);
    glVertex2d(bbox.x1, -sqrt(3)*radius);
    glEnd();

    for (int i = 0; i < 4; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * i, 0);
        glVertex2d(bbox.x0 + radius * (i+1), 0);
        glVertex2d(bbox.x0 + radius * i + radius/2, -sqrt(3)/2*radius);
        glEnd();
    }

    for (int i = 0; i < 3; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * i + radius/2, sqrt(3)/2*radius);
        glVertex2d(bbox.x0 + radius * (i+1) + radius/2, sqrt(3)/2*radius);
        glVertex2d(bbox.x0 + radius * (i+1), 0);
        glEnd();
    }

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x0, sqrt(3)/2*radius);
    glVertex2d(bbox.x0 + radius/2, sqrt(3)/2*radius);
    glVertex2d(bbox.x0, 0);
    glEnd();

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x1, sqrt(3)/2*radius);
    glVertex2d(bbox.x1 - radius/2, sqrt(3)/2*radius);
    glVertex2d(bbox.x1, 0);
    glEnd();

    for (int i = 0; i < 4; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * i, sqrt(3) * radius);
        glVertex2d(bbox.x0 + radius * (i+1), sqrt(3) * radius);
        glVertex2d(bbox.x0 + radius * i + radius/2, sqrt(3)/2*radius);
        glEnd();
    }

    for (int i = 0; i < 3; i++)
    {
        glBegin(GL_POLYGON);
        glVertex2d(bbox.x0 + radius * (i+1) - (h/sqrt(3)-radius), bbox.y1);
        glVertex2d(bbox.x0 + radius * (i+1) + (h/sqrt(3)-radius), bbox.y1);
        glVertex2d(bbox.x0 + radius * (i+1), bbox.y1 - (h - sqrt(3) * radius));
        glEnd();
    }

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x0, bbox.y1);
    glVertex2d(bbox.x0 + (h/sqrt(3)-radius), bbox.y1);
    glVertex2d(bbox.x0, bbox.y1 - (h - sqrt(3) * radius));
    glEnd();

    glBegin(GL_POLYGON);
    glVertex2d(bbox.x1, bbox.y1);
    glVertex2d(bbox.x1 - (h/sqrt(3)-radius), bbox.y1);
    glVertex2d(bbox.x1, bbox.y1 - (h - sqrt(3) * radius));
    glEnd();
}

void Koch::drawSeed()
{
    glColor3d(0.4, 0.4, 0.4);
    glBegin(GL_TRIANGLES);
    glVertex2d(20, 0);
    glVertex2d(-10, 17.32);
    glVertex2d(-10, -17.32);

    glVertex2d(-20, 0);
    glVertex2d(10, 17.32);
    glVertex2d(10, -17.32);
    glEnd();
}

Interval Koch::evalDM(Box &box, int level)
{
    Point ctBox;
    box.getCenter(ctBox);

    double value = evalDM(ctBox, ct, radius, level);

    double rBox = box.radius();
    Interval res = Interval(value - rBox, value + rBox);

    if (::numeric::Lower<Interval>(res) > 0)
    {
        box.setStatus(BOX_OUT);
    }
    else if (::numeric::Upper<Interval>(res) < 0)
    {
        box.setStatus(BOX_IN);
    }
    else
    {
        box.setStatus(BOX_BOUNDARY);
    }

    return res;
}

Interval Koch::evalFang(Box &box, int level)
{
    Point ctBox;
    box.getCenter(ctBox);

    double value = evalFang(ctBox, ct, radius/2.0, level);

    double rBox = box.radius();
    Interval res = Interval(value - rBox, value + rBox);

    if (::numeric::Lower<Interval>(res) > 0)
    {
        box.setStatus(BOX_OUT);
    }
    else if (::numeric::Upper<Interval>(res) < 0)
    {
        box.setStatus(BOX_IN);
    }
    else
    {
        box.setStatus(BOX_BOUNDARY);
    }

    return res;
}

Interval Koch::evalLattice(Box &box)
{
    Point ctBox;
    box.getCenter(ctBox);

    double x = (ctBox.x() - ctBox.y()/sqrt(3))/radius;
    double y = (ctBox.y() * 2/sqrt(3))/radius;

    double value = x - floor(x) + y - floor(y) - 1;

    double rBox = box.radius();
    Interval res = Interval(value - rBox, value + rBox);

    if (::numeric::Lower<Interval>(res) > 0)
    {
        box.setStatus(BOX_OUT);
    }
    else if (::numeric::Upper<Interval>(res) < 0)
    {
        box.setStatus(BOX_IN);
    }
    else
    {
        box.setStatus(BOX_BOUNDARY);
    }

    return res;
}

double Koch::evalFang(Point &pt, Point &c, double r, int level)
{
    double rho = pt.distance(c);
    double theta = atan2(pt.y() - c.y(), pt.x() - c.x());

    theta = PI/3.0 * distZE(3.0*theta/PI);

    Point ptMap = Point(rho * cos(theta) + c.x(), rho * sin(theta) + c.y());
    Point newCt;
    double value = baseFunc(ptMap, c, r);
    if (level == 0)
    {
        return value;
    }
    else
    {
        newCt = Point(c.x() + r*4/3, c.y());
        return std::min(value, evalFang(ptMap, newCt, r/3.0, --level));
    }
}

double Koch::evalDM(Point &pt, Point &c, double r, int level)
{
    Point newPt;
    Point newCt;

    if (level == 0)
    {
        return patternKoch(pt, radius);
    }
    else
    {
        newPt = Point(pt.x() * 3, pt.y() * 3);
        return std::min(patternKoch(pt, radius), evalDM(newPt, c, radius, --level));
    }

    double u = (pt.x() - pt.y()/sqrt(3))/r;
    double v = (2 * pt.y()/sqrt(3))/r;

    u = u - floor(u);
    v = v - floor(v);

    if (2*u + v - 1 <= 0 && u + 2*v - 1 <= 0)
    {
        newCt = Point(0, 0);
    }
    else if (u + 2*v - 2 >= 0 && 2*u + v - 2 >= 0)
    {
        newCt = Point(3*r/2, sqrt(3)*r/2);
    }
    else if (v >= u)
    {
        newCt = Point(r/2, sqrt(3)*r/2);
    }
    else
    {
        newCt = Point(r, 0);
    }

    u *= r;
    v *= r;

    newPt = Point(u + v/2, sqrt(3) * v/2) ;

    double value = baseFunc(newPt, newCt, r/4.0);

    if (level == 0)
    {
        return value;
    }
    else
    {
        newPt = Point(pt.x() * 3, pt.y() * 3);
        return std::min(value, evalDM(newPt, c, r, --level)/3);
    }
}

void Koch::generateFang(Box &box, int level)
{
    Box testedBox;
    Box subBox[4];

    do
    {
        testedBox = allBoxes.back();

        if (testedBox.getStatus() != BOX_UNKNOWN)
        {
            break;
        }

        evalFang(testedBox, level);

        allBoxes.pop_back();

        if (testedBox.getStatus() == BOX_BOUNDARY && testedBox.subdivide4(subBox, 0.05))
        {
            for (int i = 0; i < 4; i++)
            {
                allBoxes.push_back(subBox[i]);
            }
        }
        else
        {
            allBoxes.insert(allBoxes.begin(), testedBox);
        }
    }while (!quitThread);
}

double Koch::baseFunc(Point &pt, Point &c, double r)
{
    // return sqrt(pow(pt.x() - c.x(), 2) + pow(pt.y() - c.y(), 2) + pow(pt.z() - c.z(), 2)) - r;

    return baseFunc2(pt, c, r);
}

double Koch::baseFunc2(Point &pt, Point &c, double r)
{
    r *= 2;
    Triangle t1(Point(r*cos(PI/3) + c.x(), r*sin(PI/3) + c.y()),
                Point(r*cos(PI) + c.x(), r*sin(PI) + c.y()),
                Point(r*cos(5*PI/3)+ c.x(), r*sin(5*PI/3) + c.y()));
    Triangle t2(Point(r*cos(2*PI/3) + c.x(), r*sin(2*PI/3) + c.y()),
                Point(r*cos(4*PI/3) + c.x(), r*sin(4*PI/3) + c.y()),
                Point(r*cos(6*PI/3) + c.x(), r*sin(6*PI/3) + c.y()));

    return std::min(t1.distance(pt), t2.distance(pt));
}

double Koch::patternKoch(Point &pt, double r)
{
    return std::min(evenCircle(pt, r), oddCircle(pt, r));
}

double Koch::evenCircle(Point &pt, double r)
{
    double rho = 1 / sqrt(3);
    return 4 * pow(distZ(pt.x()/r/2), 2) + 12 * pow(distZ(pt.y()/r / 2 * rho), 2) - pow(rho, 2);
}

double Koch::oddCircle(Point &pt, double r)
{
    Point newPt = Point(pt.x() + 1 * r, pt.y() + sqrt(3) * r);

    return evenCircle(newPt, r);
}

void Koch::generateDM(Box &box, int level)
{
    Box testedBox;
    Box subBox[4];

    do
    {
        testedBox = allBoxes.back();

        if (testedBox.getStatus() != BOX_UNKNOWN)
        {
            break;
        }

        evalDM(testedBox, level);

        allBoxes.pop_back();

        if (testedBox.getStatus() == BOX_BOUNDARY && testedBox.subdivide4(subBox, 0.05))
        {
            for (int i = 0; i < 4; i++)
            {
                allBoxes.push_back(subBox[3-i]);
            }
        }
        else
        {
            allBoxes.insert(allBoxes.begin(), testedBox);
        }
    }while (!quitThread);
}

void Koch::generateLattice()
{
    Box testedBox;
    Box subBox[4];

    do
    {
        testedBox = latticeBoxes.back();

        if (testedBox.getStatus() != BOX_UNKNOWN)
        {
            break;
        }

        evalLattice(testedBox);

        latticeBoxes.pop_back();

        if (testedBox.getStatus() == BOX_BOUNDARY && testedBox.subdivide4(subBox, 0.01))
        {
            for (int i = 0; i < 4; i++)
            {
                latticeBoxes.push_back(subBox[3-i]);
            }
        }
        else
        {
            latticeBoxes.insert(latticeBoxes.begin(), testedBox);
        }
    }while (!quitThread);
}
